package com.uwo.commons.dubbo.configuration;

import com.alibaba.dubbo.config.*;
import com.uwo.commons.dubbo.configuration.aop.annotation.DubboReference;
import com.uwo.commons.dubbo.configuration.aop.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * dubbo配置
 * @author hao.yan
 */
@RefreshScope
@Configuration
public class DubboConfiguration implements BeanPostProcessor {

    private final Logger log = LoggerFactory.getLogger(DubboConfiguration.class);

    @Autowired
    private DubboProperties dubboProperties;

    // 应用集合
    private Map<String, Object> dubboReferences = new HashMap<String, Object>();

    /**
     * 构造方法后执行
     */
    @PostConstruct
    public void init(){
        log.info("dubbo properties = {}", dubboProperties);
    }

    /**
     * 应用
     * @return
     */
    @Bean
    @RefreshScope
    public ApplicationConfig applicationConfig() {
        return new ApplicationConfig(dubboProperties.getApplication().getName());
    }

    /**
     * 协议
     * @return
     */
    @Bean
    @RefreshScope
    public ProtocolConfig protocolConfig(){
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName(dubboProperties.getProtocol().getName());
        protocolConfig.setPort(dubboProperties.getProtocol().getPort());
        protocolConfig.setPayload(dubboProperties.getProtocol().getPayload());
        return protocolConfig;
    }

    /**
     * 用于扫描所有bean
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        // 获取class
        Class<?> clazz = bean.getClass();
        if (org.springframework.aop.support.AopUtils.isAopProxy(bean)) {
            clazz = org.springframework.aop.support.AopUtils.getTargetClass(bean);
        }

        // 查询需要注入的bean
        if(clazz.isAnnotationPresent(DubboService.class)){
            log.info("bean name = {}", beanName);

            DubboService service = clazz.getAnnotation(DubboService.class);
            Class<?> serviceClass = service.interfaceClass();
            if(serviceClass == void.class){
                Class<?>[] interfaces = bean.getClass().getInterfaces();
                if(interfaces.length == 0)
                    throw new IllegalStateException("Failed to export remote service class " + clazz.getName() + ", cause: The @Service undefined interfaceClass or interfaceName, and the service class unimplemented any interfaces.");
                serviceClass = interfaces[0];
            }

            serviceConfig(service, serviceClass, bean);
        }

        // 查询引用的bean
        for (Field field : clazz.getDeclaredFields()) {
            if(!field.isAnnotationPresent(DubboReference.class))
                continue;
            DubboReference reference = field.getAnnotation(DubboReference.class);
            Class<?> referenceClass = reference.interfaceClass();
            if(referenceClass == void.class){
                referenceClass = field.getType();
            }
            String key = referenceClass.getCanonicalName() + ":" + reference.version();
            // ReferenceConfig 不存在时
            if (!dubboReferences.containsKey(key)) {
                ReferenceConfig referenceConfig = referenceConfig(reference, referenceClass);
                dubboReferences.put(key, referenceConfig.get());
            }
            try {
                field.setAccessible(true);
                field.set(bean, dubboReferences.get(key));
                field.setAccessible(false);
            } catch (Exception e) {
                throw new BeanCreationException(beanName, e);
            }
        }

        return bean;
    }

    /**
     * 构建ServiceConfig
     * @param service
     * @param clazz
     * @param bean
     * @return
     */
    private ServiceConfig serviceConfig(DubboService service, Class<?> clazz, Object bean){
        RegistryConfig registryConfig = dubboProperties.getRegistryConfigs().get(service.registryId());
        ServiceConfig serviceConfig = new ServiceConfig();
        serviceConfig.setInterface(clazz);
        serviceConfig.setVersion(service.version());
        serviceConfig.setApplication(applicationConfig());
        serviceConfig.setProtocol(protocolConfig());
        serviceConfig.setRegistry(registryConfig);
        serviceConfig.setRef(bean);
        serviceConfig.export();
        return serviceConfig;
    }

    /**
     * 构建ReferenceConfig
     * @param reference
     * @param clazz
     * @return
     */
    private ReferenceConfig referenceConfig(DubboReference reference, Class<?> clazz){
        RegistryConfig registryConfig = dubboProperties.getRegistryConfigs().get(reference.registryId());
        ReferenceConfig referenceConfig = new ReferenceConfig();
        // 接口
        referenceConfig.setInterface(clazz);
        // id
        referenceConfig.setId(clazz.getCanonicalName());
        // 协议
        referenceConfig.setProtocol(protocolConfig().getName());
        referenceConfig.setApplication(applicationConfig());
        referenceConfig.setRegistry(registryConfig);
        referenceConfig.setVersion(reference.version());
        // 超时时间
        referenceConfig.setTimeout(registryConfig.getTimeout());
        // 校验bean是否存在
        referenceConfig.setCheck(registryConfig.isCheck());
        return referenceConfig;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}
